Libraries

Load required libraries

library(tidyverse)
── Attaching core tidyverse packages ───────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.2     ✔ tibble    3.2.1
✔ lubridate 1.9.4     ✔ tidyr     1.3.1
✔ purrr     1.0.4     
── Conflicts ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(here)
G3;here() starts at /Users/petermacpherson/Documents/Documents - Peter’s Mac mini/Projects/AQ-MTB_2025-05-14/Work/Data/aq_mtb
g
library(brms)
G3;Loading required package: Rcpp
gG3;Loading 'brms' package (version 2.22.0). Useful instructions
can be found by typing help('brms'). A more detailed introduction
to the package is available through vignette('brms_overview').
gG3;
Attaching package: ‘brms’

gG3;The following object is masked from ‘package:stats’:

    ar

g
library(tidybayes)
G3;
Attaching package: ‘tidybayes’

gG3;The following objects are masked from ‘package:brms’:

    dstudent_t, pstudent_t, qstudent_t, rstudent_t

g
library(sf)
G3;Linking to GEOS 3.13.0, GDAL 3.8.5, PROJ 9.5.1; sf_use_s2() is TRUE
g
library(mgcv)
G3;Loading required package: nlme
gG3;
Attaching package: ‘nlme’

gG3;The following object is masked from ‘package:dplyr’:

    collapse

gG3;This is mgcv 1.9-1. For overview type 'help("mgcv-package")'.
gG3;
Attaching package: ‘mgcv’

gG3;The following objects are masked from ‘package:brms’:

    s, t2

g
library(priorsense)
library(osmdata)
G3;Data (c) OpenStreetMap contributors, ODbL 1.0. https://www.openstreetmap.org/copyright
g
library(nngeo)
G3;Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
g
library(glue)
library(arrow)
G3;
Attaching package: ‘arrow’

gG3;The following object is masked from ‘package:lubridate’:

    duration

gG3;The following object is masked from ‘package:utils’:

    timestamp

g

Import data

Import the modelling dataset - cleaned and prepped in the 2025-05-12_aq_clean.Rmd script


aq_model_data <- read_rds("input_data/aq_model_data.rds")

#also load the scale clusters
load("input_data/scale_72_clusters.rda") #SCALE clusters

#and the grid data
mw_100m_cropped <- read_rds("input_data/mw_100m_cropped.rds")
mw_100m_grid_sf <- read_rds("input_data/mw_100m_grid_sf.rds")

Functions

get_st_predictions(): A function to take posterior draws for a prediction matrix, write to an arrow database (to handle exhausting memory), then summarise

get_st_predictions <- function(model, nd, model_id, out_dir, 
                               ndraws = 1000, chunk_size = 10000, 
                               who_pm25_limit = 25,
                               summarise_exposure_by = "grid_id",
                               write_summary = TRUE) {
  dir.create(out_dir, showWarnings = FALSE, recursive = TRUE)
  nd <- nd %>% dplyr::mutate(.row = dplyr::row_number())
  n_chunks <- ceiling(nrow(nd) / chunk_size)

  nd_file <- file.path(out_dir, paste0("nd_", model_id, ".parquet"))
  arrow::write_parquet(nd, nd_file)

  for (i in seq_len(n_chunks)) {
    cat("Processing chunk", i, "of", n_chunks, "\n")
    idx <- ((i - 1) * chunk_size + 1):min(i * chunk_size, nrow(nd))
    nd_chunk <- nd[idx, ]

    epred_array <- posterior_epred(model, newdata = nd_chunk, ndraws = ndraws)

    epred_df <- as.data.frame.table(epred_array, responseName = "epred") %>%
      dplyr::rename(draw = Var1, row_in_chunk = Var2) %>%
      dplyr::mutate(
        draw = as.integer(draw),
        row_in_chunk = as.integer(row_in_chunk),
        .row = idx[row_in_chunk],
        model_id = model_id
      ) %>%
      dplyr::select(draw, .row, epred, model_id)

    arrow::write_parquet(
      epred_df,
      sink = file.path(out_dir, glue::glue("epred_chunk_{i}.parquet"))
    )

    rm(epred_array, epred_df, nd_chunk)
    gc()
  }

  # Load full draws and metadata for summarisation
  ds <- arrow::open_dataset(out_dir)
  nd_ds <- arrow::open_dataset(nd_file)

  joined <- ds %>%
    dplyr::left_join(nd_ds, by = ".row") %>%
    dplyr::mutate(
      epred_pm25 = exp(epred),
      days = lubridate::days_in_month(date),
      exceed = epred_pm25 > who_pm25_limit
    )

  # Per-.row posterior summaries (as before)
  row_summary <- joined %>%
    dplyr::group_by(.row) %>%
    dplyr::summarise(
      mean_log_epred = mean(epred, na.rm = TRUE),
      sd_log_epred = sd(epred, na.rm = TRUE),
      lwr_log = quantile(epred, 0.025, na.rm = TRUE),
      upr_log = quantile(epred, 0.975, na.rm = TRUE),

      mean_epred = mean(epred_pm25, na.rm = TRUE),
      sd_epred = sd(epred_pm25, na.rm = TRUE),
      lwr = quantile(epred_pm25, 0.025, na.rm = TRUE),
      upr = quantile(epred_pm25, 0.975, na.rm = TRUE),

      prob_exceed = mean(exceed, na.rm = TRUE),
      .groups = "drop"
    ) %>%
    dplyr::collect() %>%
    dplyr::left_join(nd, by = ".row") %>%
    dplyr::mutate(date = lubridate::month(date, label = TRUE))

  # Additional exposure summaries across draws
  exposure_summary <- joined %>%
    dplyr::group_by_at(c(summarise_exposure_by, "draw")) %>%
    dplyr::summarise(
      cum_exposure = sum(epred_pm25 * days, na.rm = TRUE),
      mean_exposure = mean(epred_pm25, na.rm = TRUE),
      prob_exceed = mean(exceed, na.rm = TRUE),
      .groups = "drop"
    ) %>%
    dplyr::collect() %>%
    dplyr::group_by_at(summarise_exposure_by) %>%
    dplyr::summarise(
      mean_pm25 = mean(mean_exposure),
      sd_pm25 = sd(mean_exposure),
      lwr_pm25 = quantile(mean_exposure, 0.025),
      upr_pm25 = quantile(mean_exposure, 0.975),

      mean_cum = mean(cum_exposure),
      sd_cum = sd(cum_exposure),
      lwr_cum = quantile(cum_exposure, 0.025),
      upr_cum = quantile(cum_exposure, 0.975),

      prob_exceed = mean(prob_exceed),
      .groups = "drop"
    )

  # Optionally write to disk
  if (write_summary) {
    arrow::write_parquet(row_summary, file.path(out_dir, paste0("summary_", model_id, "_row.parquet")))
    arrow::write_parquet(exposure_summary, file.path(out_dir, paste0("summary_", model_id, "_exposure.parquet")))
  }

  return(list(row_summary = row_summary, exposure_summary = exposure_summary))
}

plot_st_predictions(): A function to plot spatiotemporal predictions from model posteriors

compute_exposure_metrics() calculates exposure metrics for the 1 year p

PM2.5

First we will model PM2.5

Model 1: Day of year only

“We fitted a spatially smooth regression model for log-transformed PM2.5 concentrations using a Gaussian process (GP) smooth over spatial coordinates (x, y) to capture flexible spatial trends. Seasonal variation was modelled using a Fourier series expansion of day-of-year up to the 3rd harmonic (i.e., sine and cosine terms for annual, semi-annual, and tri-annual cycles) to account for complex seasonal patterns. The model was fitted in a Bayesian framework using brms with a Gaussian likelihood and the cmdstanr backend.”


priors <- c(
  prior(normal(3.4, 1), class = "Intercept"),
  prior(normal(0,1), class = "b"),
  prior(exponential(1), class = "sigma"),
  prior(exponential(1), class = "sdgp"),
  prior(normal(0, 1), class = "lscale", lb = 0)
)

Fit prior only model


m1_prior <- brm(
    formula = log_pm2_5 ~ gp(x, y, k=15) +
      sin_doy1 + cos_doy1 +
      sin_doy2 + cos_doy2 +
      sin_doy3 + cos_doy3,
    data = aq_model_data,
    family = gaussian(),
    prior = priors,
    sample_prior = "only",
    chains = 4, cores = 4,
    backend = "cmdstanr"
  )
G2;H2;Warningh: The global prior 'normal(0, 1)' of class 'lscale' will not be used in the model as all related coefficients have individual priors already. If you did not set those priors yourself, then maybe brms has assigned default priors. See ?set_prior and ?default_prior for more details.g
G3;Compiling Stan program...
g

-

\

|

/

-

\

|

/

-

\

|

/

-

\

|

/

-

\

|

/

-

\

|

/

-

\

|

/

-

\

|

/

-

\

|

/

-

\

|

/

-

\

|

/

-

 
G3;Start sampling
g
Running MCMC with 4 parallel chains...

Chain 1 Iteration:    1 / 2000 [  0%]  (Warmup) 
Chain 1 Iteration:  100 / 2000 [  5%]  (Warmup) 
Chain 1 Iteration:  200 / 2000 [ 10%]  (Warmup) 
Chain 1 Iteration:  300 / 2000 [ 15%]  (Warmup) 
Chain 1 Iteration:  400 / 2000 [ 20%]  (Warmup) 
Chain 1 Iteration:  500 / 2000 [ 25%]  (Warmup) 
Chain 1 Iteration:  600 / 2000 [ 30%]  (Warmup) 
Chain 1 Iteration:  700 / 2000 [ 35%]  (Warmup) 
Chain 1 Iteration:  800 / 2000 [ 40%]  (Warmup) 
Chain 1 Iteration:  900 / 2000 [ 45%]  (Warmup) 
Chain 1 Iteration: 1000 / 2000 [ 50%]  (Warmup) 
Chain 1 Iteration: 1001 / 2000 [ 50%]  (Sampling) 
Chain 1 Iteration: 1100 / 2000 [ 55%]  (Sampling) 
Chain 1 Iteration: 1200 / 2000 [ 60%]  (Sampling) 
Chain 1 Iteration: 1300 / 2000 [ 65%]  (Sampling) 
Chain 1 Iteration: 1400 / 2000 [ 70%]  (Sampling) 
Chain 1 Iteration: 1500 / 2000 [ 75%]  (Sampling) 
Chain 2 Iteration:    1 / 2000 [  0%]  (Warmup) 
Chain 2 Iteration:  100 / 2000 [  5%]  (Warmup) 
Chain 2 Iteration:  200 / 2000 [ 10%]  (Warmup) 
Chain 2 Iteration:  300 / 2000 [ 15%]  (Warmup) 
Chain 2 Iteration:  400 / 2000 [ 20%]  (Warmup) 
Chain 2 Iteration:  500 / 2000 [ 25%]  (Warmup) 
Chain 2 Iteration:  600 / 2000 [ 30%]  (Warmup) 
Chain 2 Iteration:  700 / 2000 [ 35%]  (Warmup) 
Chain 2 Iteration:  800 / 2000 [ 40%]  (Warmup) 
Chain 2 Iteration:  900 / 2000 [ 45%]  (Warmup) 
Chain 2 Iteration: 1000 / 2000 [ 50%]  (Warmup) 
Chain 2 Iteration: 1001 / 2000 [ 50%]  (Sampling) 
Chain 2 Iteration: 1100 / 2000 [ 55%]  (Sampling) 
Chain 2 Iteration: 1200 / 2000 [ 60%]  (Sampling) 
Chain 2 Iteration: 1300 / 2000 [ 65%]  (Sampling) 
Chain 2 Iteration: 1400 / 2000 [ 70%]  (Sampling) 
Chain 3 Iteration:    1 / 2000 [  0%]  (Warmup) 
Chain 3 Iteration:  100 / 2000 [  5%]  (Warmup) 
Chain 3 Iteration:  200 / 2000 [ 10%]  (Warmup) 
Chain 3 Iteration:  300 / 2000 [ 15%]  (Warmup) 
Chain 3 Iteration:  400 / 2000 [ 20%]  (Warmup) 
Chain 3 Iteration:  500 / 2000 [ 25%]  (Warmup) 
Chain 3 Iteration:  600 / 2000 [ 30%]  (Warmup) 
Chain 3 Iteration:  700 / 2000 [ 35%]  (Warmup) 
Chain 3 Iteration:  800 / 2000 [ 40%]  (Warmup) 
Chain 3 Iteration:  900 / 2000 [ 45%]  (Warmup) 
Chain 3 Iteration: 1000 / 2000 [ 50%]  (Warmup) 
Chain 3 Iteration: 1001 / 2000 [ 50%]  (Sampling) 
Chain 3 Iteration: 1100 / 2000 [ 55%]  (Sampling) 
Chain 3 Iteration: 1200 / 2000 [ 60%]  (Sampling) 
Chain 3 Iteration: 1300 / 2000 [ 65%]  (Sampling) 
Chain 4 Iteration:    1 / 2000 [  0%]  (Warmup) 
Chain 4 Iteration:  100 / 2000 [  5%]  (Warmup) 
Chain 4 Iteration:  200 / 2000 [ 10%]  (Warmup) 
Chain 4 Iteration:  300 / 2000 [ 15%]  (Warmup) 
Chain 4 Iteration:  400 / 2000 [ 20%]  (Warmup) 
Chain 4 Iteration:  500 / 2000 [ 25%]  (Warmup) 
Chain 4 Iteration:  600 / 2000 [ 30%]  (Warmup) 
Chain 4 Iteration:  700 / 2000 [ 35%]  (Warmup) 
Chain 4 Iteration:  800 / 2000 [ 40%]  (Warmup) 
Chain 4 Iteration:  900 / 2000 [ 45%]  (Warmup) 
Chain 4 Iteration: 1000 / 2000 [ 50%]  (Warmup) 
Chain 4 Iteration: 1001 / 2000 [ 50%]  (Sampling) 
Chain 4 Iteration: 1100 / 2000 [ 55%]  (Sampling) 
Chain 4 Iteration: 1200 / 2000 [ 60%]  (Sampling) 
Chain 4 Iteration: 1300 / 2000 [ 65%]  (Sampling) 
Chain 1 Iteration: 1600 / 2000 [ 80%]  (Sampling) 
Chain 1 Iteration: 1700 / 2000 [ 85%]  (Sampling) 
Chain 1 Iteration: 1800 / 2000 [ 90%]  (Sampling) 
Chain 1 Iteration: 1900 / 2000 [ 95%]  (Sampling) 
Chain 1 Iteration: 2000 / 2000 [100%]  (Sampling) 
Chain 2 Iteration: 1500 / 2000 [ 75%]  (Sampling) 
Chain 2 Iteration: 1600 / 2000 [ 80%]  (Sampling) 
Chain 2 Iteration: 1700 / 2000 [ 85%]  (Sampling) 
Chain 2 Iteration: 1800 / 2000 [ 90%]  (Sampling) 
Chain 2 Iteration: 1900 / 2000 [ 95%]  (Sampling) 
Chain 2 Iteration: 2000 / 2000 [100%]  (Sampling) 
Chain 3 Iteration: 1400 / 2000 [ 70%]  (Sampling) 
Chain 3 Iteration: 1500 / 2000 [ 75%]  (Sampling) 
Chain 3 Iteration: 1600 / 2000 [ 80%]  (Sampling) 
Chain 3 Iteration: 1700 / 2000 [ 85%]  (Sampling) 
Chain 3 Iteration: 1800 / 2000 [ 90%]  (Sampling) 
Chain 3 Iteration: 1900 / 2000 [ 95%]  (Sampling) 
Chain 3 Iteration: 2000 / 2000 [100%]  (Sampling) 
Chain 4 Iteration: 1400 / 2000 [ 70%]  (Sampling) 
Chain 4 Iteration: 1500 / 2000 [ 75%]  (Sampling) 
Chain 4 Iteration: 1600 / 2000 [ 80%]  (Sampling) 
Chain 4 Iteration: 1700 / 2000 [ 85%]  (Sampling) 
Chain 4 Iteration: 1800 / 2000 [ 90%]  (Sampling) 
Chain 4 Iteration: 1900 / 2000 [ 95%]  (Sampling) 
Chain 4 Iteration: 2000 / 2000 [100%]  (Sampling) 
Chain 1 finished in 0.2 seconds.
Chain 2 finished in 0.2 seconds.
Chain 3 finished in 0.2 seconds.
Chain 4 finished in 0.2 seconds.

All 4 chains finished successfully.
Mean chain execution time: 0.2 seconds.
Total execution time: 0.4 seconds.
G3;Loading required namespace: rstan
g
# summary(m1_prior)
# plot(m1_prior)

Now model with data.

m1 <- brm(
    formula = log_pm2_5 ~ gp(x, y, k=15)  +
    sin_doy1 + cos_doy1 +
    sin_doy2 + cos_doy2 +
    sin_doy3 + cos_doy3,
    data = aq_model_data,
    family = gaussian(),
    prior = priors,
    chains = 4, cores = 4,
    backend = "cmdstanr"
  )
G2;H2;Warningh: The global prior 'normal(0, 1)' of class 'lscale' will not be used in the model as all related coefficients have individual priors already. If you did not set those priors yourself, then maybe brms has assigned default priors. See ?set_prior and ?default_prior for more details.g
G3;Model executable is up to date!
gG3;Start sampling
g
Running MCMC with 4 parallel chains...

Chain 1 Iteration:    1 / 2000 [  0%]  (Warmup) 
Chain 2 Iteration:    1 / 2000 [  0%]  (Warmup) 
Chain 3 Iteration:    1 / 2000 [  0%]  (Warmup) 
Chain 4 Iteration:    1 / 2000 [  0%]  (Warmup) 
Chain 2 Iteration:  100 / 2000 [  5%]  (Warmup) 
Chain 1 Iteration:  100 / 2000 [  5%]  (Warmup) 
Chain 3 Iteration:  100 / 2000 [  5%]  (Warmup) 
Chain 4 Iteration:  100 / 2000 [  5%]  (Warmup) 
Chain 1 Iteration:  200 / 2000 [ 10%]  (Warmup) 
Chain 2 Iteration:  200 / 2000 [ 10%]  (Warmup) 
Chain 4 Iteration:  200 / 2000 [ 10%]  (Warmup) 
Chain 3 Iteration:  200 / 2000 [ 10%]  (Warmup) 
Chain 3 Iteration:  300 / 2000 [ 15%]  (Warmup) 
Chain 1 Iteration:  300 / 2000 [ 15%]  (Warmup) 
Chain 2 Iteration:  300 / 2000 [ 15%]  (Warmup) 
Chain 4 Iteration:  300 / 2000 [ 15%]  (Warmup) 
Chain 3 Iteration:  400 / 2000 [ 20%]  (Warmup) 
Chain 4 Iteration:  400 / 2000 [ 20%]  (Warmup) 
Chain 1 Iteration:  400 / 2000 [ 20%]  (Warmup) 
Chain 2 Iteration:  400 / 2000 [ 20%]  (Warmup) 
Chain 4 Iteration:  500 / 2000 [ 25%]  (Warmup) 
Chain 1 Iteration:  500 / 2000 [ 25%]  (Warmup) 
Chain 3 Iteration:  500 / 2000 [ 25%]  (Warmup) 
Chain 2 Iteration:  500 / 2000 [ 25%]  (Warmup) 
Chain 4 Iteration:  600 / 2000 [ 30%]  (Warmup) 
Chain 1 Iteration:  600 / 2000 [ 30%]  (Warmup) 
Chain 3 Iteration:  600 / 2000 [ 30%]  (Warmup) 
Chain 2 Iteration:  600 / 2000 [ 30%]  (Warmup) 
Chain 4 Iteration:  700 / 2000 [ 35%]  (Warmup) 
Chain 1 Iteration:  700 / 2000 [ 35%]  (Warmup) 
Chain 3 Iteration:  700 / 2000 [ 35%]  (Warmup) 
Chain 2 Iteration:  700 / 2000 [ 35%]  (Warmup) 
Chain 4 Iteration:  800 / 2000 [ 40%]  (Warmup) 
Chain 1 Iteration:  800 / 2000 [ 40%]  (Warmup) 
Chain 3 Iteration:  800 / 2000 [ 40%]  (Warmup) 
Chain 2 Iteration:  800 / 2000 [ 40%]  (Warmup) 
Chain 4 Iteration:  900 / 2000 [ 45%]  (Warmup) 
Chain 1 Iteration:  900 / 2000 [ 45%]  (Warmup) 
Chain 3 Iteration:  900 / 2000 [ 45%]  (Warmup) 
Chain 2 Iteration:  900 / 2000 [ 45%]  (Warmup) 
Chain 4 Iteration: 1000 / 2000 [ 50%]  (Warmup) 
Chain 4 Iteration: 1001 / 2000 [ 50%]  (Sampling) 
Chain 1 Iteration: 1000 / 2000 [ 50%]  (Warmup) 
Chain 1 Iteration: 1001 / 2000 [ 50%]  (Sampling) 
Chain 3 Iteration: 1000 / 2000 [ 50%]  (Warmup) 
Chain 3 Iteration: 1001 / 2000 [ 50%]  (Sampling) 
Chain 2 Iteration: 1000 / 2000 [ 50%]  (Warmup) 
Chain 2 Iteration: 1001 / 2000 [ 50%]  (Sampling) 
Chain 4 Iteration: 1100 / 2000 [ 55%]  (Sampling) 
Chain 4 Iteration: 1200 / 2000 [ 60%]  (Sampling) 
Chain 1 Iteration: 1100 / 2000 [ 55%]  (Sampling) 
Chain 3 Iteration: 1100 / 2000 [ 55%]  (Sampling) 
Chain 2 Iteration: 1100 / 2000 [ 55%]  (Sampling) 
Chain 4 Iteration: 1300 / 2000 [ 65%]  (Sampling) 
Chain 1 Iteration: 1200 / 2000 [ 60%]  (Sampling) 
Chain 4 Iteration: 1400 / 2000 [ 70%]  (Sampling) 
Chain 3 Iteration: 1200 / 2000 [ 60%]  (Sampling) 
Chain 2 Iteration: 1200 / 2000 [ 60%]  (Sampling) 
Chain 4 Iteration: 1500 / 2000 [ 75%]  (Sampling) 
Chain 1 Iteration: 1300 / 2000 [ 65%]  (Sampling) 
Chain 4 Iteration: 1600 / 2000 [ 80%]  (Sampling) 
Chain 3 Iteration: 1300 / 2000 [ 65%]  (Sampling) 
Chain 2 Iteration: 1300 / 2000 [ 65%]  (Sampling) 
Chain 4 Iteration: 1700 / 2000 [ 85%]  (Sampling) 
Chain 1 Iteration: 1400 / 2000 [ 70%]  (Sampling) 
Chain 4 Iteration: 1800 / 2000 [ 90%]  (Sampling) 
Chain 3 Iteration: 1400 / 2000 [ 70%]  (Sampling) 
Chain 2 Iteration: 1400 / 2000 [ 70%]  (Sampling) 
Chain 4 Iteration: 1900 / 2000 [ 95%]  (Sampling) 
Chain 1 Iteration: 1500 / 2000 [ 75%]  (Sampling) 
Chain 4 Iteration: 2000 / 2000 [100%]  (Sampling) 
Chain 4 finished in 91.6 seconds.
Chain 3 Iteration: 1500 / 2000 [ 75%]  (Sampling) 
Chain 2 Iteration: 1500 / 2000 [ 75%]  (Sampling) 
Chain 1 Iteration: 1600 / 2000 [ 80%]  (Sampling) 
Chain 3 Iteration: 1600 / 2000 [ 80%]  (Sampling) 
Chain 2 Iteration: 1600 / 2000 [ 80%]  (Sampling) 
Chain 1 Iteration: 1700 / 2000 [ 85%]  (Sampling) 
Chain 3 Iteration: 1700 / 2000 [ 85%]  (Sampling) 
Chain 2 Iteration: 1700 / 2000 [ 85%]  (Sampling) 
Chain 1 Iteration: 1800 / 2000 [ 90%]  (Sampling) 
Chain 3 Iteration: 1800 / 2000 [ 90%]  (Sampling) 
Chain 2 Iteration: 1800 / 2000 [ 90%]  (Sampling) 
Chain 1 Iteration: 1900 / 2000 [ 95%]  (Sampling) 
Chain 3 Iteration: 1900 / 2000 [ 95%]  (Sampling) 
Chain 2 Iteration: 1900 / 2000 [ 95%]  (Sampling) 
Chain 1 Iteration: 2000 / 2000 [100%]  (Sampling) 
Chain 1 finished in 124.1 seconds.
Chain 3 Iteration: 2000 / 2000 [100%]  (Sampling) 
Chain 3 finished in 125.7 seconds.
Chain 2 Iteration: 2000 / 2000 [100%]  (Sampling) 
Chain 2 finished in 127.5 seconds.

All 4 chains finished successfully.
Mean chain execution time: 117.2 seconds.
Total execution time: 127.7 seconds.
# summary(m1)
# conditional_effects(m1)
# pp_check(m1)
# plot(m1)

Compare prior-only model to model with data using priorsense package:


m1_draws <- as_draws_df(m1)

# Extract prior and posterior draws
# powerscale_sensitivity(m1, variable = c("b_Intercept", 
#                                         "b_sin_doy1", "b_cos_doy1",
#                                         "b_sin_doy2", "b_cos_doy2",
#                                         "b_sin_doy3", "b_cos_doy3",
#                                         "sdgp_gpxy", "sigma", "Intercept"))
# 
# powerscale_plot_dens(m1, variable = c("b_Intercept", 
#                                         "b_sin_doy1", "b_cos_doy1",
#                                         "b_sin_doy2", "b_cos_doy2",
#                                         "b_sin_doy3", "b_cos_doy3",
#                                         "sdgp_gpxy", "sigma", "Intercept"))

Predictions by space and time. Here, although we only have data within clusters, we will predict for cells outside of clusters based on covariates. We will also predict for the month for whcih we do not have data.

set up the predictions matrix

#set_up prediction date frame
nd_m1 <- mw_100m_grid_sf %>%
  sf::st_sf() %>%
  mutate(x = st_coordinates(st_centroid(.))[, 1],
         y = st_coordinates(st_centroid(.))[, 2]) %>%
  st_drop_geometry() %>%
  rename(pop_density = 1) %>%
  mutate(pop_density_km2 = pop_density / 0.01)
G2;H2;Warningh: There were 2 warnings in `stopifnot()`.
The first warning was:
ℹ In argument: `x = st_coordinates(st_centroid(.))[, 1]`.
Caused by warning:
! st_centroid assumes attributes are constant over geometries
ℹ Run ]8;;ide:run:dplyr::last_dplyr_]8;;ide:run:warnings()warnings()]8;;dplyr::last_dplyr_]8;;ide:run:warnings()warnings()]8;;]8;; to see the 1 remaining warning.g

Get the spatiotemporal predictions for model 1

m1_sum <- get_st_predictions(model = m1, nd=nd_m1, model_id = "m1", out_dir = "epred_chunks_m1", ndraws = 1000, chunk_size = 10000, who_pm25_limit = 25)
Processing chunk 1 of 38 
Processing chunk 2 of 38 
Processing chunk 3 of 38 
Processing chunk 4 of 38 
Processing chunk 5 of 38 
Processing chunk 6 of 38 
Processing chunk 7 of 38 
Processing chunk 8 of 38 
Processing chunk 9 of 38 
Processing chunk 10 of 38 
Processing chunk 11 of 38 
Processing chunk 12 of 38 
Processing chunk 13 of 38 
Processing chunk 14 of 38 
Processing chunk 15 of 38 
Processing chunk 16 of 38 
Processing chunk 17 of 38 
Processing chunk 18 of 38 
Processing chunk 19 of 38 
Processing chunk 20 of 38 
Processing chunk 21 of 38 
Processing chunk 22 of 38 
Processing chunk 23 of 38 
Processing chunk 24 of 38 
Processing chunk 25 of 38 
Processing chunk 26 of 38 
Processing chunk 27 of 38 
Processing chunk 28 of 38 
Processing chunk 29 of 38 
Processing chunk 30 of 38 
Processing chunk 31 of 38 
Processing chunk 32 of 38 
Processing chunk 33 of 38 
Processing chunk 34 of 38 
Processing chunk 35 of 38 
Processing chunk 36 of 38 
Processing chunk 37 of 38 
Processing chunk 38 of 38 

Plot predictions for model 1

m1_plots %>% map(plot)
$mean_log_plot

$mean_plot

$sd_plot

$exceed_plot

Now just draw 50 random coordinates, and predict by time to see whether we captured the trend.

#sample coordinate points from the household dataset
#we will fix the small number sampled later!
set.seed(123) 
sampled_points_m1 <- aq_model_data %>%
  sample_n(50) %>%
  select(x, y)

#generate DOY 0–365 and calculate Fourier terms
doy_grid <- tibble(doy = 0:365) %>%
  mutate(
    sin_doy1 = sin(2 * pi * doy / 365),
    cos_doy1 = cos(2 * pi * doy / 365),
    sin_doy2 = sin(4 * pi * doy / 365),
    cos_doy2 = cos(4 * pi * doy / 365),
    sin_doy3 = sin(6 * pi * doy / 365),
    cos_doy3 = cos(6 * pi * doy / 365)
  )

#expand grid across sampled locations
prediction_df_m1 <- sampled_points_m1 %>%
  crossing(doy_grid)

#add predictions
preds_m1 <- add_epred_draws(object = m1, newdata = prediction_df_m1)

#summarise
preds_m1_sum <- preds_m1 %>%
  ungroup() %>%
  group_by(doy) %>%
  summarise(.epred_mean = mean(.epred),
            .lower = quantile(.epred, probs=0.025),
            .upper = quantile(.epred, probs = 0.975))

#GEt the observed data
observed_data <- aq_model_data %>%
  select(mean_doy, log_pm2_5) 

#plot
preds_m1_sum %>%
  ggplot() +
  geom_ribbon(aes(x=doy, ymin=.lower, ymax = .upper), fill="steelblue", alpha=0.3) +
  geom_line(aes(x=doy, y=.epred_mean)) +
  geom_jitter(data = observed_data, aes(x = mean_doy, y = log_pm2_5),
              color = "darkred", alpha = 0.6, width = 0.5, height = 0.0, size = 1.2) +
  labs(title = "Model-estimated log(PM2.5) with empirical measurements",
       subtitle = "Model predictions with 95% CrI and observed data points",
       x = "Day of year",
       y = "log(PM2.5)",
       caption = "Modelled estimates restricted to within clusters") +
  theme_ggdist() +
  theme(panel.background = element_rect(colour = "grey78"),
        legend.position = "none")

Model 2: With covariates

Now we include grid level covariates of distance to the road, population density, and building footprint percent, along witht the mean temp and huidity on the day of measurement (from the purple air monitors)

Again, priors to be fixed later

priors <- c(
  prior(normal(3.4, 1), class = "Intercept"),
  prior(normal(0,1), class = "b"),
  prior(exponential(1), class = "sigma"),
  prior(exponential(1), class = "sdgp"),
  prior(normal(0, 1), class = "lscale", lb = 0)
)


m2 <- brm(
    formula = log_pm2_5 ~ gp(x, y, k=15)  +
      s(mean_temp_c, k=5) + 
      s(mean_current_humidity, k=5) +
      s(pop_density_km2, k=5) +
      s(building_coverage_pct, k=5) +
      s(dist_to_road_m, k=5) +
      sin_doy1 + cos_doy1 +
      sin_doy2 + cos_doy2 +
      sin_doy3 + cos_doy3,
    data = aq_model_data,
    family = gaussian(),
    prior = priors,
    chains = 4, cores = 4,
    backend = "cmdstanr"
  )
G2;H2;Warningh: The global prior 'normal(0, 1)' of class 'lscale' will not be used in the model as all related coefficients have individual priors already. If you did not set those priors yourself, then maybe brms has assigned default priors. See ?set_prior and ?default_prior for more details.g
G3;Compiling Stan program...
g

-

\

|

/

-

\

|

/

-

\

|

/

-

\

|

/

-

\

|

/

-

\

|

/

-

\

|

/

-

\

|

/

-

\

|

/

-

\

|

/

-

\

|

/

-

\

|

/

-

\

|

/

-

\

|

/

-

\

|

/

 
G3;Start sampling
g
Running MCMC with 4 parallel chains...

Chain 1 Iteration:    1 / 2000 [  0%]  (Warmup) 
Chain 2 Iteration:    1 / 2000 [  0%]  (Warmup) 
Chain 3 Iteration:    1 / 2000 [  0%]  (Warmup) 
Chain 4 Iteration:    1 / 2000 [  0%]  (Warmup) 
Chain 1 Iteration:  100 / 2000 [  5%]  (Warmup) 
Chain 2 Iteration:  100 / 2000 [  5%]  (Warmup) 
Chain 4 Iteration:  100 / 2000 [  5%]  (Warmup) 
Chain 3 Iteration:  100 / 2000 [  5%]  (Warmup) 
Chain 4 Iteration:  200 / 2000 [ 10%]  (Warmup) 
Chain 1 Iteration:  200 / 2000 [ 10%]  (Warmup) 
Chain 2 Iteration:  200 / 2000 [ 10%]  (Warmup) 
Chain 3 Iteration:  200 / 2000 [ 10%]  (Warmup) 
Chain 4 Iteration:  300 / 2000 [ 15%]  (Warmup) 
Chain 1 Iteration:  300 / 2000 [ 15%]  (Warmup) 
Chain 2 Iteration:  300 / 2000 [ 15%]  (Warmup) 
Chain 3 Iteration:  300 / 2000 [ 15%]  (Warmup) 
Chain 4 Iteration:  400 / 2000 [ 20%]  (Warmup) 
Chain 1 Iteration:  400 / 2000 [ 20%]  (Warmup) 
Chain 2 Iteration:  400 / 2000 [ 20%]  (Warmup) 
Chain 3 Iteration:  400 / 2000 [ 20%]  (Warmup) 
Chain 4 Iteration:  500 / 2000 [ 25%]  (Warmup) 
Chain 1 Iteration:  500 / 2000 [ 25%]  (Warmup) 
Chain 2 Iteration:  500 / 2000 [ 25%]  (Warmup) 
Chain 3 Iteration:  500 / 2000 [ 25%]  (Warmup) 
Chain 4 Iteration:  600 / 2000 [ 30%]  (Warmup) 
Chain 1 Iteration:  600 / 2000 [ 30%]  (Warmup) 
Chain 2 Iteration:  600 / 2000 [ 30%]  (Warmup) 
Chain 3 Iteration:  600 / 2000 [ 30%]  (Warmup) 
Chain 4 Iteration:  700 / 2000 [ 35%]  (Warmup) 
Chain 1 Iteration:  700 / 2000 [ 35%]  (Warmup) 
Chain 2 Iteration:  700 / 2000 [ 35%]  (Warmup) 
Chain 3 Iteration:  700 / 2000 [ 35%]  (Warmup) 
Chain 4 Iteration:  800 / 2000 [ 40%]  (Warmup) 
Chain 1 Iteration:  800 / 2000 [ 40%]  (Warmup) 
Chain 2 Iteration:  800 / 2000 [ 40%]  (Warmup) 
Chain 3 Iteration:  800 / 2000 [ 40%]  (Warmup) 
Chain 1 Iteration:  900 / 2000 [ 45%]  (Warmup) 
Chain 4 Iteration:  900 / 2000 [ 45%]  (Warmup) 
Chain 2 Iteration:  900 / 2000 [ 45%]  (Warmup) 
Chain 3 Iteration:  900 / 2000 [ 45%]  (Warmup) 
Chain 1 Iteration: 1000 / 2000 [ 50%]  (Warmup) 
Chain 1 Iteration: 1001 / 2000 [ 50%]  (Sampling) 
Chain 4 Iteration: 1000 / 2000 [ 50%]  (Warmup) 
Chain 4 Iteration: 1001 / 2000 [ 50%]  (Sampling) 
Chain 2 Iteration: 1000 / 2000 [ 50%]  (Warmup) 
Chain 2 Iteration: 1001 / 2000 [ 50%]  (Sampling) 
Chain 3 Iteration: 1000 / 2000 [ 50%]  (Warmup) 
Chain 3 Iteration: 1001 / 2000 [ 50%]  (Sampling) 
Chain 1 Iteration: 1100 / 2000 [ 55%]  (Sampling) 
Chain 4 Iteration: 1100 / 2000 [ 55%]  (Sampling) 
Chain 2 Iteration: 1100 / 2000 [ 55%]  (Sampling) 
Chain 3 Iteration: 1100 / 2000 [ 55%]  (Sampling) 
Chain 1 Iteration: 1200 / 2000 [ 60%]  (Sampling) 
Chain 2 Iteration: 1200 / 2000 [ 60%]  (Sampling) 
Chain 4 Iteration: 1200 / 2000 [ 60%]  (Sampling) 
Chain 3 Iteration: 1200 / 2000 [ 60%]  (Sampling) 
Chain 2 Iteration: 1300 / 2000 [ 65%]  (Sampling) 
Chain 3 Iteration: 1300 / 2000 [ 65%]  (Sampling) 
Chain 1 Iteration: 1300 / 2000 [ 65%]  (Sampling) 
Chain 4 Iteration: 1300 / 2000 [ 65%]  (Sampling) 
Chain 2 Iteration: 1400 / 2000 [ 70%]  (Sampling) 
Chain 3 Iteration: 1400 / 2000 [ 70%]  (Sampling) 
Chain 1 Iteration: 1400 / 2000 [ 70%]  (Sampling) 
Chain 4 Iteration: 1400 / 2000 [ 70%]  (Sampling) 
Chain 3 Iteration: 1500 / 2000 [ 75%]  (Sampling) 
Chain 2 Iteration: 1500 / 2000 [ 75%]  (Sampling) 
Chain 1 Iteration: 1500 / 2000 [ 75%]  (Sampling) 
Chain 4 Iteration: 1500 / 2000 [ 75%]  (Sampling) 
Chain 3 Iteration: 1600 / 2000 [ 80%]  (Sampling) 
Chain 2 Iteration: 1600 / 2000 [ 80%]  (Sampling) 
Chain 1 Iteration: 1600 / 2000 [ 80%]  (Sampling) 
Chain 4 Iteration: 1600 / 2000 [ 80%]  (Sampling) 
Chain 3 Iteration: 1700 / 2000 [ 85%]  (Sampling) 
Chain 2 Iteration: 1700 / 2000 [ 85%]  (Sampling) 
Chain 1 Iteration: 1700 / 2000 [ 85%]  (Sampling) 
Chain 3 Iteration: 1800 / 2000 [ 90%]  (Sampling) 
Chain 4 Iteration: 1700 / 2000 [ 85%]  (Sampling) 
Chain 2 Iteration: 1800 / 2000 [ 90%]  (Sampling) 
Chain 1 Iteration: 1800 / 2000 [ 90%]  (Sampling) 
Chain 3 Iteration: 1900 / 2000 [ 95%]  (Sampling) 
Chain 2 Iteration: 1900 / 2000 [ 95%]  (Sampling) 
Chain 4 Iteration: 1800 / 2000 [ 90%]  (Sampling) 
Chain 3 Iteration: 2000 / 2000 [100%]  (Sampling) 
Chain 3 finished in 217.0 seconds.
Chain 1 Iteration: 1900 / 2000 [ 95%]  (Sampling) 
Chain 2 Iteration: 2000 / 2000 [100%]  (Sampling) 
Chain 2 finished in 220.5 seconds.
Chain 4 Iteration: 1900 / 2000 [ 95%]  (Sampling) 
Chain 1 Iteration: 2000 / 2000 [100%]  (Sampling) 
Chain 1 finished in 230.3 seconds.
Chain 4 Iteration: 2000 / 2000 [100%]  (Sampling) 
Chain 4 finished in 234.8 seconds.

All 4 chains finished successfully.
Mean chain execution time: 225.7 seconds.
Total execution time: 235.4 seconds.
G3;Warning: 4 of 4000 (0.0%) transitions ended with a divergence.
See https://mc-stan.org/misc/warnings for details.

g

# m2_draws <- as_draws_df(m2)
# 
# # Extract prior and posterior draws
# powerscale_sensitivity(m2, variable = c("b_Intercept", "b_sin_doy1", "b_cos_doy1", "b_sin_doy2", "b_cos_doy2", "b_sin_doy3", "b_cos_doy3", "bs_smean_temp_c_1", "bs_smean_current_humidity_1", "bs_spop_density_km2_1", "bs_sbuilding_coverage_pct_1", "bs_sdist_to_road_m_1",         
# "sds_smean_temp_c_1", "sds_smean_current_humidity_1", "sds_spop_density_km2_1", "sds_sbuilding_coverage_pct_1", 
# "sds_sdist_to_road_m_1", "sdgp_gpxy", "lscale_gpxy", "sigma", "Intercept", "s_smean_temp_c_1[1]", "s_smean_temp_c_1[2]", "s_smean_temp_c_1[3]",         "s_smean_current_humidity_1[1]",  "s_smean_current_humidity_1[2]",  "s_smean_current_humidity_1[3]",  "s_spop_density_km2_1[1]",       
#  "s_spop_density_km2_1[2]",        "s_spop_density_km2_1[3]",        "s_sbuilding_coverage_pct_1[1]",  "s_sbuilding_coverage_pct_1[2]", 
#  "s_sbuilding_coverage_pct_1[3]",  "s_sdist_to_road_m_1[1]", "s_sdist_to_road_m_1[2]", "s_sdist_to_road_m_1[3]"))
# 
# powerscale_plot_dens(m2, variable = c("b_Intercept", "b_sin_doy1", "b_cos_doy1", "b_sin_doy2", "b_cos_doy2", "b_sin_doy3", "b_cos_doy3", "bs_smean_temp_c_1", "bs_smean_current_humidity_1", "bs_spop_density_km2_1", "bs_sbuilding_coverage_pct_1", "bs_sdist_to_road_m_1",         
# "sds_smean_temp_c_1", "sds_smean_current_humidity_1", "sds_spop_density_km2_1", "sds_sbuilding_coverage_pct_1", 
# "sds_sdist_to_road_m_1", "sdgp_gpxy", "lscale_gpxy", "sigma", "Intercept", "s_smean_temp_c_1[1]", "s_smean_temp_c_1[2]", "s_smean_temp_c_1[3]",         "s_smean_current_humidity_1[1]",  "s_smean_current_humidity_1[2]",  "s_smean_current_humidity_1[3]",  "s_spop_density_km2_1[1]",       
#  "s_spop_density_km2_1[2]",        "s_spop_density_km2_1[3]",        "s_sbuilding_coverage_pct_1[1]",  "s_sbuilding_coverage_pct_1[2]", 
#  "s_sbuilding_coverage_pct_1[3]",  "s_sdist_to_road_m_1[1]", "s_sdist_to_road_m_1[2]", "s_sdist_to_road_m_1[3]"))

Get predictions for model 2

m2_sum <- get_st_predictions(model = m2, nd=nd_m2, model_id = "m2", out_dir = "epred_chunks_m2", ndraws = 1000, chunk_size = 10000, who_pm25_limit = 25)
Processing chunk 1 of 38 
Processing chunk 2 of 38 
Processing chunk 3 of 38 
Processing chunk 4 of 38 
Processing chunk 5 of 38 
Processing chunk 6 of 38 
Processing chunk 7 of 38 
Processing chunk 8 of 38 
Processing chunk 9 of 38 
Processing chunk 10 of 38 
Processing chunk 11 of 38 
Processing chunk 12 of 38 
Processing chunk 13 of 38 
Processing chunk 14 of 38 
Processing chunk 15 of 38 
Processing chunk 16 of 38 
Processing chunk 17 of 38 
Processing chunk 18 of 38 
Processing chunk 19 of 38 
Processing chunk 20 of 38 
Processing chunk 21 of 38 
Processing chunk 22 of 38 
Processing chunk 23 of 38 
Processing chunk 24 of 38 
Processing chunk 25 of 38 
Processing chunk 26 of 38 
Processing chunk 27 of 38 
Processing chunk 28 of 38 
Processing chunk 29 of 38 
Processing chunk 30 of 38 
Processing chunk 31 of 38 
Processing chunk 32 of 38 
Processing chunk 33 of 38 
Processing chunk 34 of 38 
Processing chunk 35 of 38 
Processing chunk 36 of 38 
Processing chunk 37 of 38 
Processing chunk 38 of 38 

Plot predictions for model 2

m2_plots %>% map(plot)
$mean_log_plot

$mean_plot

$sd_plot

$exceed_plot

Sample coordinates, and plot over time


#sample coordinate points
#will sort out the small number later
set.seed(123)
sampled_points_m2 <- aq_model_data %>%
  sample_n(50) %>%
  select(x, y, mean_temp_c, mean_current_humidity, pop_density_km2, building_coverage_pct, dist_to_road_m, grid_id, log_pm2_5)

#generate DOY 0–365 and calculate Fourier terms
doy_grid <- tibble(doy = seq(0,365, by=1)) %>%
  mutate(
    sin_doy1 = sin(2 * pi * doy / 365),
    cos_doy1 = cos(2 * pi * doy / 365),
    sin_doy2 = sin(4 * pi * doy / 365),
    cos_doy2 = cos(4 * pi * doy / 365),
    sin_doy3 = sin(6 * pi * doy / 365),
    cos_doy3 = cos(6 * pi * doy / 365)
  )

#expand grid across sampled locations
prediction_df_m2 <- sampled_points_m2 %>%
  crossing(doy_grid)

#add predictions
preds_m2 <- add_epred_draws(object = m2, newdata = prediction_df_m2, ndraws = 100)

#observed data for plotting
observed_data <- aq_model_data %>%
  select(mean_doy, log_pm2_5) %>%
  mutate(mean_doy = round(mean_doy))

#plot
preds_m2 %>%
  ungroup() %>%
  ggplot(aes(x=doy)) +
  stat_lineribbon(aes(y=.epred), .width=0.95) + 
  geom_jitter(data = observed_data, aes(x = mean_doy, y = log_pm2_5),
              color = "darkred", alpha = 0.6, width = 0.5, height = 0.0, size = 1.2) +
  scale_fill_brewer() +
  labs(title = "Model-estimated log(PM2.5) with empirical measurements",
       subtitle = "Model predictions with 95% CrI and observed data points",
       x = "Day of year",
       y = "log(PM2.5)",
       caption = "Modelled estimates restricted to within clusters") +
  theme_ggdist() +
  theme(panel.background = element_rect(colour = "grey78"),
        legend.position = "none")

Compare models, using LOO CV


library(loo)

loo_m1 <- loo(m1)
loo_m2 <- loo(m2)

loo_compare(loo_m1, loo_m2)

PM10

Now to model PM10 data

TODO

LS0tCnRpdGxlOiAiQmxhbnR5cmUgQWlyIFF1YWxpdHkgTW9kZWxsaW5nIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyBMaWJyYXJpZXMKCkxvYWQgcmVxdWlyZWQgbGlicmFyaWVzCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoaGVyZSkKbGlicmFyeShicm1zKQpsaWJyYXJ5KHRpZHliYXllcykKbGlicmFyeShzZikKbGlicmFyeShtZ2N2KQpsaWJyYXJ5KHByaW9yc2Vuc2UpCmxpYnJhcnkob3NtZGF0YSkKbGlicmFyeShubmdlbykKbGlicmFyeShnbHVlKQpsaWJyYXJ5KGFycm93KQpgYGAKCiMjIEltcG9ydCBkYXRhCgpJbXBvcnQgdGhlIG1vZGVsbGluZyBkYXRhc2V0IC0gY2xlYW5lZCBhbmQgcHJlcHBlZCBpbiB0aGUgYDIwMjUtMDUtMTJfYXFfY2xlYW4uUm1kYCBzY3JpcHQKCmBgYHtyfQoKYXFfbW9kZWxfZGF0YSA8LSByZWFkX3JkcygiaW5wdXRfZGF0YS9hcV9tb2RlbF9kYXRhLnJkcyIpCgojYWxzbyBsb2FkIHRoZSBzY2FsZSBjbHVzdGVycwpsb2FkKCJpbnB1dF9kYXRhL3NjYWxlXzcyX2NsdXN0ZXJzLnJkYSIpICNTQ0FMRSBjbHVzdGVycwoKI2FuZCB0aGUgZ3JpZCBkYXRhCm13XzEwMG1fY3JvcHBlZCA8LSByZWFkX3JkcygiaW5wdXRfZGF0YS9td18xMDBtX2Nyb3BwZWQucmRzIikKbXdfMTAwbV9ncmlkX3NmIDwtIHJlYWRfcmRzKCJpbnB1dF9kYXRhL213XzEwMG1fZ3JpZF9zZi5yZHMiKQoKCmBgYAoKCiMjIEZ1bmN0aW9ucwoKYGdldF9zdF9wcmVkaWN0aW9ucygpYDogQSBmdW5jdGlvbiB0byB0YWtlIHBvc3RlcmlvciBkcmF3cyBmb3IgYSBwcmVkaWN0aW9uIG1hdHJpeCwgd3JpdGUgdG8gYW4gYGFycm93YCBkYXRhYmFzZSAodG8gaGFuZGxlIGV4aGF1c3RpbmcgbWVtb3J5KSwgdGhlbiBzdW1tYXJpc2UKCmBgYHtyfQoKZ2V0X3N0X3ByZWRpY3Rpb25zIDwtIGZ1bmN0aW9uKG1vZGVsLCBuZCwgbW9kZWxfaWQsIG91dF9kaXIsIG5kcmF3cyA9IDEwMDAsIGNodW5rX3NpemUgPSAxMDAwMCwgd2hvX3BtMjVfbGltaXQgPSAyNSkgewogICMgQ3JlYXRlIG91dHB1dCBmb2xkZXIKICBkaXIuY3JlYXRlKG91dF9kaXIsIHNob3dXYXJuaW5ncyA9IEZBTFNFLCByZWN1cnNpdmUgPSBUUlVFKQoKICBuZCA8LSBuZCAlPiUgbXV0YXRlKC5yb3cgPSByb3dfbnVtYmVyKCkpCiAgbl9jaHVua3MgPC0gY2VpbGluZyhucm93KG5kKSAvIGNodW5rX3NpemUpCgogICMgU2F2ZSBkZXNpZ24gbWF0cml4IHRvIGRpc2sgZm9yIGxhdGVyIGpvaW5zCiAgbmRfZmlsZSA8LSBmaWxlLnBhdGgob3V0X2RpciwgcGFzdGUwKCJuZF8iLCBtb2RlbF9pZCwgIi5wYXJxdWV0IikpCiAgYXJyb3c6OndyaXRlX3BhcnF1ZXQobmQsIG5kX2ZpbGUpCgogICMgTG9vcCBvdmVyIGNodW5rcwogIGZvciAoaSBpbiBzZXFfbGVuKG5fY2h1bmtzKSkgewogICAgY2F0KCJQcm9jZXNzaW5nIGNodW5rIiwgaSwgIm9mIiwgbl9jaHVua3MsICJcbiIpCgogICAgaWR4IDwtICgoaSAtIDEpICogY2h1bmtfc2l6ZSArIDEpOm1pbihpICogY2h1bmtfc2l6ZSwgbnJvdyhuZCkpCiAgICBuZF9jaHVuayA8LSBuZFtpZHgsIF0KCiAgICBlcHJlZF9hcnJheSA8LSBwb3N0ZXJpb3JfZXByZWQobW9kZWwsIG5ld2RhdGEgPSBuZF9jaHVuaywgbmRyYXdzID0gbmRyYXdzKQoKICAgIGVwcmVkX2RmIDwtIGFzLmRhdGEuZnJhbWUudGFibGUoZXByZWRfYXJyYXksIHJlc3BvbnNlTmFtZSA9ICJlcHJlZCIpCiAgICBjb2xuYW1lcyhlcHJlZF9kZikgPC0gYygiZHJhdyIsICJyb3dfaW5fY2h1bmsiLCAiZXByZWQiKQoKICAgIGVwcmVkX2RmIDwtIGVwcmVkX2RmICU+JQogICAgICBtdXRhdGUoCiAgICAgICAgZHJhdyA9IGFzLmludGVnZXIoZHJhdyksCiAgICAgICAgcm93X2luX2NodW5rID0gYXMuaW50ZWdlcihyb3dfaW5fY2h1bmspLAogICAgICAgIC5yb3cgPSBpZHhbcm93X2luX2NodW5rXSwKICAgICAgICBtb2RlbF9pZCA9IG1vZGVsX2lkCiAgICAgICkgJT4lCiAgICAgIHNlbGVjdChkcmF3LCAucm93LCBlcHJlZCwgbW9kZWxfaWQpCgogICAgYXJyb3c6OndyaXRlX3BhcnF1ZXQoCiAgICAgIGVwcmVkX2RmLAogICAgICBzaW5rID0gZmlsZS5wYXRoKG91dF9kaXIsIGdsdWU6OmdsdWUoImVwcmVkX2NodW5rX3tpfS5wYXJxdWV0IikpCiAgICApCgogICAgcm0oZXByZWRfYXJyYXksIGVwcmVkX2RmLCBuZF9jaHVuaykKICAgIGdjKCkKICB9CgogICMgUmVhZCBmcm9tIGRpc2sKICBkcyA8LSBhcnJvdzo6b3Blbl9kYXRhc2V0KG91dF9kaXIpCiAgbmRfZHMgPC0gYXJyb3c6Om9wZW5fZGF0YXNldChuZF9maWxlKQoKICBzdW1tYXJ5X2RmIDwtIGRzICU+JQogICAgbXV0YXRlKGVwcmVkX3BtMjUgPSBleHAoZXByZWQpKSAlPiUKICAgIGdyb3VwX2J5KC5yb3cpICU+JQogICAgc3VtbWFyaXNlKAogICAgICBtZWFuX2xvZ19lcHJlZCA9IG1lYW4oZXByZWQsIG5hLnJtID0gVFJVRSksCiAgICAgIHNkX2xvZ19lcHJlZCA9IHNkKGVwcmVkLCBuYS5ybSA9IFRSVUUpLAogICAgICBsd3JfbG9nID0gcXVhbnRpbGUoZXByZWQsIDAuMDI1LCBuYS5ybSA9IFRSVUUpLAogICAgICB1cHJfbG9nID0gcXVhbnRpbGUoZXByZWQsIDAuOTc1LCBuYS5ybSA9IFRSVUUpLAogICAgICAKICAgICAgbWVhbl9lcHJlZCA9IG1lYW4oZXByZWRfcG0yNSwgbmEucm0gPSBUUlVFKSwKICAgICAgc2RfZXByZWQgPSBzZChlcHJlZF9wbTI1LCBuYS5ybSA9IFRSVUUpLAogICAgICBsd3IgPSBxdWFudGlsZShlcHJlZF9wbTI1LCAwLjAyNSwgbmEucm0gPSBUUlVFKSwKICAgICAgdXByID0gcXVhbnRpbGUoZXByZWRfcG0yNSwgMC45NzUsIG5hLnJtID0gVFJVRSksCiAgICAgIAogICAgICBwcm9iX2V4Y2VlZCA9IG1lYW4oZXByZWRfcG0yNSA+IHdob19wbTI1X2xpbWl0LCBuYS5ybSA9IFRSVUUpLAogICAgICAuZ3JvdXBzID0gImRyb3AiCiAgICApICU+JQogICAgY29sbGVjdCgpICU+JQogICAgbGVmdF9qb2luKG5kLCBieSA9ICIucm93IikgJT4lCiAgICBtdXRhdGUoZGF0ZSA9IGx1YnJpZGF0ZTo6bW9udGgoZGF0ZSwgbGFiZWwgPSBUUlVFKSkKCiAgcmV0dXJuKHN1bW1hcnlfZGYpCn0KCgpgYGAKCmBwbG90X3N0X3ByZWRpY3Rpb25zKClgOiBBIGZ1bmN0aW9uIHRvIHBsb3Qgc3BhdGlvdGVtcG9yYWwgcHJlZGljdGlvbnMgZnJvbSBtb2RlbCBwb3N0ZXJpb3JzCgpgYGB7cn0KCnBsb3Rfc3RfcHJlZGljdGlvbnMgPC0gZnVuY3Rpb24oc3VtbWFyeV9kZiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9zZiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZV9saW1pdHMgPSBsaXN0KG1lYW4gPSBOVUxMLCBzZCA9IE5VTEwsIHByb2IgPSBjKDAsIDEpKSkgewogIHJlcXVpcmUoZ2dwbG90MikKICByZXF1aXJlKHZpcmlkaXMpCiAgcmVxdWlyZShnZ2Rpc3QpCiAgcmVxdWlyZShkcGx5cikKICByZXF1aXJlKGx1YnJpZGF0ZSkKCiAgIyBNZWFuIHByZWRpY3Rpb24gcGxvdHNzCiAgICBwMSA8LSBzdW1tYXJ5X2RmICU+JQogICAgZ2dwbG90KCkgKwogICAgZ2VvbV90aWxlKGFlcyh4ID0geCwgeSA9IHksIGZpbGwgPSBtZWFuX2xvZ19lcHJlZCkpICsKICAgIGdlb21fc2YoZGF0YSA9IGNsdXN0ZXJfc2YsIGNvbG91ciA9ICJncmV5NzgiLCBmaWxsID0gTkEpICsKICAgIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJEIiwgbGltaXRzID0gdmFsdWVfbGltaXRzJG1lYW4pICsKICAgIGZhY2V0X3dyYXAofmRhdGUpICsKICAgIGxhYnModGl0bGUgPSAiUG9zdGVyaW9yIG1lYW4gb2YgbG9nKFBNMi41KSAowrVnL23CsykiLCB4ID0gIiIsIHkgPSAiIikgKwogICAgdGhlbWVfZ2dkaXN0KCkgKwogICAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gTkEsIGNvbG91ciA9ICJncmV5NzgiKSwKICAgICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksCiAgICAgICAgICBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5NzgiKSkKICAgIAogIHAyIDwtIHN1bW1hcnlfZGYgJT4lCiAgICBnZ3Bsb3QoKSArCiAgICBnZW9tX3RpbGUoYWVzKHggPSB4LCB5ID0geSwgZmlsbCA9IG1lYW5fZXByZWQpKSArCiAgICBnZW9tX3NmKGRhdGEgPSBjbHVzdGVyX3NmLCBjb2xvdXIgPSAiZ3JleTc4IiwgZmlsbCA9IE5BKSArCiAgICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb24gPSAiRCIsIGxpbWl0cyA9IHZhbHVlX2xpbWl0cyRtZWFuKSArCiAgICBmYWNldF93cmFwKH5kYXRlKSArCiAgICBsYWJzKHRpdGxlID0gIlBvc3RlcmlvciBtZWFuIG9mIFBNMi41ICjCtWcvbcKzKSIsIHggPSAiIiwgeSA9ICIiKSArCiAgICB0aGVtZV9nZ2Rpc3QoKSArCiAgICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSBOQSwgY29sb3VyID0gImdyZXk3OCIpLAogICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwKICAgICAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXk3OCIpKQoKICAjIFN0YW5kYXJkIGRldmlhdGlvbiBwbG90CiAgcDMgPC0gc3VtbWFyeV9kZiAlPiUKICAgIGdncGxvdCgpICsKICAgIGdlb21fdGlsZShhZXMoeCA9IHgsIHkgPSB5LCBmaWxsID0gc2RfZXByZWQpKSArCiAgICBnZW9tX3NmKGRhdGEgPSBjbHVzdGVyX3NmLCBjb2xvdXIgPSAiZ3JleTc4IiwgZmlsbCA9IE5BKSArCiAgICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb24gPSAiRiIsIGxpbWl0cyA9IHZhbHVlX2xpbWl0cyRzZCkgKwogICAgZmFjZXRfd3JhcCh+ZGF0ZSkgKwogICAgbGFicyh0aXRsZSA9ICJQb3N0ZXJpb3IgU0Qgb2YgUE0yLjUgKMK1Zy9twrMpIiwgeCA9ICIiLCB5ID0gIiIpICsKICAgIHRoZW1lX2dnZGlzdCgpICsKICAgIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9IE5BLCBjb2xvdXIgPSAiZ3JleTc4IiksCiAgICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLAogICAgICAgICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleTc4IikpCgogICMgRXhjZWVkYW5jZSBwcm9iYWJpbGl0eSBwbG90CiAgcDQgPC0gc3VtbWFyeV9kZiAlPiUKICAgIGdncGxvdCgpICsKICAgIGdlb21fdGlsZShhZXMoeCA9IHgsIHkgPSB5LCBmaWxsID0gcHJvYl9leGNlZWQpKSArCiAgICBnZW9tX3NmKGRhdGEgPSBjbHVzdGVyX3NmLCBjb2xvdXIgPSAiZ3JleTc4IiwgZmlsbCA9IE5BKSArCiAgICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb24gPSAiQyIsIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoKSwgbGltaXRzID0gdmFsdWVfbGltaXRzJHByb2IpICsKICAgIGZhY2V0X3dyYXAofmRhdGUpICsKICAgIGxhYnModGl0bGUgPSAiUHIoUE0yLjUgPiAyNSDCtWcvbcKzKSIsIHggPSAiIiwgeSA9ICIiKSArCiAgICB0aGVtZV9nZ2Rpc3QoKSArCiAgICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSBOQSwgY29sb3VyID0gImdyZXk3OCIpLAogICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwKICAgICAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXk3OCIpKQoKICBsaXN0KG1lYW5fbG9nX3Bsb3QgPSBwMSwgbWVhbl9wbG90ID0gcDIsIHNkX3Bsb3QgPSBwMywgZXhjZWVkX3Bsb3QgPSBwNCkKfQoKCgoKYGBgCgoKYGNvbXB1dGVfZXhwb3N1cmVfbWV0cmljcygpYCBjYWxjdWxhdGVzIGV4cG9zdXJlIG1ldHJpY3MgZm9yIHRoZSAxIHllYXIgcAoKCiMjIFBNMi41CgpGaXJzdCB3ZSB3aWxsIG1vZGVsIFBNMi41CgojIyMgTW9kZWwgMTogRGF5IG9mIHllYXIgb25seQoKCiJXZSBmaXR0ZWQgYSBzcGF0aWFsbHkgc21vb3RoIHJlZ3Jlc3Npb24gbW9kZWwgZm9yIGxvZy10cmFuc2Zvcm1lZCBQTTIuNSBjb25jZW50cmF0aW9ucyB1c2luZyBhIEdhdXNzaWFuIHByb2Nlc3MgKEdQKSBzbW9vdGggb3ZlciBzcGF0aWFsIGNvb3JkaW5hdGVzICh4LCB5KSB0byBjYXB0dXJlIGZsZXhpYmxlIHNwYXRpYWwgdHJlbmRzLiBTZWFzb25hbCB2YXJpYXRpb24gd2FzIG1vZGVsbGVkIHVzaW5nIGEgRm91cmllciBzZXJpZXMgZXhwYW5zaW9uIG9mIGRheS1vZi15ZWFyIHVwIHRvIHRoZSAzcmQgaGFybW9uaWMgKGkuZS4sIHNpbmUgYW5kIGNvc2luZSB0ZXJtcyBmb3IgYW5udWFsLCBzZW1pLWFubnVhbCwgYW5kIHRyaS1hbm51YWwgY3ljbGVzKSB0byBhY2NvdW50IGZvciBjb21wbGV4IHNlYXNvbmFsIHBhdHRlcm5zLiBUaGUgbW9kZWwgd2FzIGZpdHRlZCBpbiBhIEJheWVzaWFuIGZyYW1ld29yayB1c2luZyBicm1zIHdpdGggYSBHYXVzc2lhbiBsaWtlbGlob29kIGFuZCB0aGUgY21kc3RhbnIgYmFja2VuZC4iCgpgYGB7ciwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTEyfQoKcHJpb3JzIDwtIGMoCiAgcHJpb3Iobm9ybWFsKDMuNCwgMSksIGNsYXNzID0gIkludGVyY2VwdCIpLAogIHByaW9yKG5vcm1hbCgwLDEpLCBjbGFzcyA9ICJiIiksCiAgcHJpb3IoZXhwb25lbnRpYWwoMSksIGNsYXNzID0gInNpZ21hIiksCiAgcHJpb3IoZXhwb25lbnRpYWwoMSksIGNsYXNzID0gInNkZ3AiKSwKICBwcmlvcihub3JtYWwoMCwgMSksIGNsYXNzID0gImxzY2FsZSIsIGxiID0gMCkKKQoKYGBgCgpGaXQgcHJpb3Igb25seSBtb2RlbAoKYGBge3IsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD0xMn0KCm0xX3ByaW9yIDwtIGJybSgKICAgIGZvcm11bGEgPSBsb2dfcG0yXzUgfiBncCh4LCB5LCBrPTE1KSArCiAgICAgIHNpbl9kb3kxICsgY29zX2RveTEgKwogICAgICBzaW5fZG95MiArIGNvc19kb3kyICsKICAgICAgc2luX2RveTMgKyBjb3NfZG95MywKICAgIGRhdGEgPSBhcV9tb2RlbF9kYXRhLAogICAgZmFtaWx5ID0gZ2F1c3NpYW4oKSwKICAgIHByaW9yID0gcHJpb3JzLAogICAgc2FtcGxlX3ByaW9yID0gIm9ubHkiLAogICAgY2hhaW5zID0gNCwgY29yZXMgPSA0LAogICAgYmFja2VuZCA9ICJjbWRzdGFuciIKICApCgoKIyBzdW1tYXJ5KG0xX3ByaW9yKQojIHBsb3QobTFfcHJpb3IpCgpgYGAKCk5vdyBtb2RlbCB3aXRoIGRhdGEuCgoKYGBge3IsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD0xMn0KbTEgPC0gYnJtKAogICAgZm9ybXVsYSA9IGxvZ19wbTJfNSB+IGdwKHgsIHksIGs9MTUpICArCiAgICBzaW5fZG95MSArIGNvc19kb3kxICsKICAgIHNpbl9kb3kyICsgY29zX2RveTIgKwogICAgc2luX2RveTMgKyBjb3NfZG95MywKICAgIGRhdGEgPSBhcV9tb2RlbF9kYXRhLAogICAgZmFtaWx5ID0gZ2F1c3NpYW4oKSwKICAgIHByaW9yID0gcHJpb3JzLAogICAgY2hhaW5zID0gNCwgY29yZXMgPSA0LAogICAgYmFja2VuZCA9ICJjbWRzdGFuciIKICApCgojIHN1bW1hcnkobTEpCiMgY29uZGl0aW9uYWxfZWZmZWN0cyhtMSkKIyBwcF9jaGVjayhtMSkKIyBwbG90KG0xKQoKYGBgCgpDb21wYXJlIHByaW9yLW9ubHkgbW9kZWwgdG8gbW9kZWwgd2l0aCBkYXRhIHVzaW5nIGBwcmlvcnNlbnNlYCBwYWNrYWdlOgoKYGBge3IsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD0xMn0KCm0xX2RyYXdzIDwtIGFzX2RyYXdzX2RmKG0xKQoKIyBFeHRyYWN0IHByaW9yIGFuZCBwb3N0ZXJpb3IgZHJhd3MKIyBwb3dlcnNjYWxlX3NlbnNpdGl2aXR5KG0xLCB2YXJpYWJsZSA9IGMoImJfSW50ZXJjZXB0IiwgCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJiX3Npbl9kb3kxIiwgImJfY29zX2RveTEiLAojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYl9zaW5fZG95MiIsICJiX2Nvc19kb3kyIiwKIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImJfc2luX2RveTMiLCAiYl9jb3NfZG95MyIsCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzZGdwX2dweHkiLCAic2lnbWEiLCAiSW50ZXJjZXB0IikpCiMgCiMgcG93ZXJzY2FsZV9wbG90X2RlbnMobTEsIHZhcmlhYmxlID0gYygiYl9JbnRlcmNlcHQiLCAKIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImJfc2luX2RveTEiLCAiYl9jb3NfZG95MSIsCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJiX3Npbl9kb3kyIiwgImJfY29zX2RveTIiLAojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYl9zaW5fZG95MyIsICJiX2Nvc19kb3kzIiwKIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInNkZ3BfZ3B4eSIsICJzaWdtYSIsICJJbnRlcmNlcHQiKSkKYGBgCgpQcmVkaWN0aW9ucyBieSBzcGFjZSBhbmQgdGltZS4gSGVyZSwgYWx0aG91Z2ggd2Ugb25seSBoYXZlIGRhdGEgd2l0aGluIGNsdXN0ZXJzLCB3ZSB3aWxsIHByZWRpY3QgZm9yIGNlbGxzIG91dHNpZGUgb2YgY2x1c3RlcnMgYmFzZWQgb24gY292YXJpYXRlcy4KV2Ugd2lsbCBhbHNvIHByZWRpY3QgZm9yIHRoZSBtb250aCBmb3Igd2hjaWggd2UgZG8gbm90IGhhdmUgZGF0YS4KCnNldCB1cCB0aGUgcHJlZGljdGlvbnMgbWF0cml4CgpgYGB7ciwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTEyfQoKI3NldF91cCBwcmVkaWN0aW9uIGRhdGUgZnJhbWUKbmRfbTEgPC0gbXdfMTAwbV9ncmlkX3NmICU+JQogIHNmOjpzdF9zZigpICU+JQogIG11dGF0ZSh4ID0gc3RfY29vcmRpbmF0ZXMoc3RfY2VudHJvaWQoLikpWywgMV0sCiAgICAgICAgIHkgPSBzdF9jb29yZGluYXRlcyhzdF9jZW50cm9pZCguKSlbLCAyXSkgJT4lCiAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JQogIHJlbmFtZShwb3BfZGVuc2l0eSA9IDEpICU+JQogIG11dGF0ZShwb3BfZGVuc2l0eV9rbTIgPSBwb3BfZGVuc2l0eSAvIDAuMDEpCgojZ2V0IHRoZSBmaXJzdCBkYXkgb2YgZWFjaCBtb250aCBmb3IgcHJlZGljdGlvbgpmaXJzdF9kYXlzIDwtIHltZChwYXN0ZTAoIjIwMjAtIiwgc3ByaW50ZigiJTAyZCIsIDE6MTIpLCAiLTAxIikpCmZpcnN0X2RheV9kb3kgPC0geWRheShmaXJzdF9kYXlzKQoKI2NhbGN1bGF0ZSBGb3VyaWVyIHRlcm1zCmZpcnN0X2RheV9zZWFzb25hbGl0eSA8LSB0aWJibGUoCiAgbWVhbl9kb3kgPSBmaXJzdF9kYXlfZG95LAogIHNpbl9kb3kxID0gc2luKDIgKiBwaSAqIG1lYW5fZG95IC8gMzY1LjI1KSwKICBjb3NfZG95MSA9IGNvcygyICogcGkgKiBtZWFuX2RveSAvIDM2NS4yNSksCiAgc2luX2RveTIgPSBzaW4oNCAqIHBpICogbWVhbl9kb3kgLyAzNjUuMjUpLAogIGNvc19kb3kyID0gY29zKDQgKiBwaSAqIG1lYW5fZG95IC8gMzY1LjI1KSwKICBzaW5fZG95MyA9IHNpbig2ICogcGkgKiBtZWFuX2RveSAvIDM2NS4yNSksCiAgY29zX2RveTMgPSBjb3MoNiAqIHBpICogbWVhbl9kb3kgLyAzNjUuMjUpLAogIGRhdGUgPSBmaXJzdF9kYXlzCikKCiNleHBhbmQgZ3JpZCBmb3IgcHJlZGljdGlvbgpuZF9tMSA8LSBuZF9tMSAlPiUKICBjcm9zc2luZyhmaXJzdF9kYXlfc2Vhc29uYWxpdHkpICU+JQogIG11dGF0ZShidWlsZGluZ19jb3ZlcmFnZV9wY3QgPSBidWlsZGluZ19jb3ZlcmFnZV9wY3QvMTAwKQoKYGBgCgoKR2V0IHRoZSBzcGF0aW90ZW1wb3JhbCBwcmVkaWN0aW9ucyBmb3IgbW9kZWwgMQoKCmBgYHtyfQptMV9zdW0gPC0gZ2V0X3N0X3ByZWRpY3Rpb25zKG1vZGVsID0gbTEsIG5kPW5kX20xLCBtb2RlbF9pZCA9ICJtMSIsIG91dF9kaXIgPSAiZXByZWRfY2h1bmtzX20xIiwgbmRyYXdzID0gMTAwMCwgY2h1bmtfc2l6ZSA9IDEwMDAwLCB3aG9fcG0yNV9saW1pdCA9IDI1KQoKYGBgCgpQbG90IHByZWRpY3Rpb25zIGZvciBtb2RlbCAxCgpgYGB7cn0KbTFfcGxvdHMgPC0gcGxvdF9zdF9wcmVkaWN0aW9ucyhzdW1tYXJ5X2RmID0gbTFfc3VtLCBjbHVzdGVyX3NmID0gc2NhbGVfNzJfY2x1c3RlcnMpCgptMV9wbG90cyAlPiUgbWFwKHBsb3QpCgpgYGAKCgoKTm93IGp1c3QgZHJhdyA1MCByYW5kb20gY29vcmRpbmF0ZXMsIGFuZCBwcmVkaWN0IGJ5IHRpbWUgdG8gc2VlIHdoZXRoZXIgd2UgY2FwdHVyZWQgdGhlIHRyZW5kLgoKYGBge3IsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD0xMn0KI3NhbXBsZSBjb29yZGluYXRlIHBvaW50cyBmcm9tIHRoZSBob3VzZWhvbGQgZGF0YXNldAojd2Ugd2lsbCBmaXggdGhlIHNtYWxsIG51bWJlciBzYW1wbGVkIGxhdGVyIQpzZXQuc2VlZCgxMjMpIApzYW1wbGVkX3BvaW50c19tMSA8LSBhcV9tb2RlbF9kYXRhICU+JQogIHNhbXBsZV9uKDUwKSAlPiUKICBzZWxlY3QoeCwgeSkKCiNnZW5lcmF0ZSBET1kgMOKAkzM2NSBhbmQgY2FsY3VsYXRlIEZvdXJpZXIgdGVybXMKZG95X2dyaWQgPC0gdGliYmxlKGRveSA9IDA6MzY1KSAlPiUKICBtdXRhdGUoCiAgICBzaW5fZG95MSA9IHNpbigyICogcGkgKiBkb3kgLyAzNjUpLAogICAgY29zX2RveTEgPSBjb3MoMiAqIHBpICogZG95IC8gMzY1KSwKICAgIHNpbl9kb3kyID0gc2luKDQgKiBwaSAqIGRveSAvIDM2NSksCiAgICBjb3NfZG95MiA9IGNvcyg0ICogcGkgKiBkb3kgLyAzNjUpLAogICAgc2luX2RveTMgPSBzaW4oNiAqIHBpICogZG95IC8gMzY1KSwKICAgIGNvc19kb3kzID0gY29zKDYgKiBwaSAqIGRveSAvIDM2NSkKICApCgojZXhwYW5kIGdyaWQgYWNyb3NzIHNhbXBsZWQgbG9jYXRpb25zCnByZWRpY3Rpb25fZGZfbTEgPC0gc2FtcGxlZF9wb2ludHNfbTEgJT4lCiAgY3Jvc3NpbmcoZG95X2dyaWQpCgojYWRkIHByZWRpY3Rpb25zCnByZWRzX20xIDwtIGFkZF9lcHJlZF9kcmF3cyhvYmplY3QgPSBtMSwgbmV3ZGF0YSA9IHByZWRpY3Rpb25fZGZfbTEpCgojc3VtbWFyaXNlCnByZWRzX20xX3N1bSA8LSBwcmVkc19tMSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgZ3JvdXBfYnkoZG95KSAlPiUKICBzdW1tYXJpc2UoLmVwcmVkX21lYW4gPSBtZWFuKC5lcHJlZCksCiAgICAgICAgICAgIC5sb3dlciA9IHF1YW50aWxlKC5lcHJlZCwgcHJvYnM9MC4wMjUpLAogICAgICAgICAgICAudXBwZXIgPSBxdWFudGlsZSguZXByZWQsIHByb2JzID0gMC45NzUpKQoKI0dFdCB0aGUgb2JzZXJ2ZWQgZGF0YQpvYnNlcnZlZF9kYXRhIDwtIGFxX21vZGVsX2RhdGEgJT4lCiAgc2VsZWN0KG1lYW5fZG95LCBsb2dfcG0yXzUpIAoKI3Bsb3QKcHJlZHNfbTFfc3VtICU+JQogIGdncGxvdCgpICsKICBnZW9tX3JpYmJvbihhZXMoeD1kb3ksIHltaW49Lmxvd2VyLCB5bWF4ID0gLnVwcGVyKSwgZmlsbD0ic3RlZWxibHVlIiwgYWxwaGE9MC4zKSArCiAgZ2VvbV9saW5lKGFlcyh4PWRveSwgeT0uZXByZWRfbWVhbikpICsKICBnZW9tX2ppdHRlcihkYXRhID0gb2JzZXJ2ZWRfZGF0YSwgYWVzKHggPSBtZWFuX2RveSwgeSA9IGxvZ19wbTJfNSksCiAgICAgICAgICAgICAgY29sb3IgPSAiZGFya3JlZCIsIGFscGhhID0gMC42LCB3aWR0aCA9IDAuNSwgaGVpZ2h0ID0gMC4wLCBzaXplID0gMS4yKSArCiAgbGFicyh0aXRsZSA9ICJNb2RlbC1lc3RpbWF0ZWQgbG9nKFBNMi41KSB3aXRoIGVtcGlyaWNhbCBtZWFzdXJlbWVudHMiLAogICAgICAgc3VidGl0bGUgPSAiTW9kZWwgcHJlZGljdGlvbnMgd2l0aCA5NSUgQ3JJIGFuZCBvYnNlcnZlZCBkYXRhIHBvaW50cyIsCiAgICAgICB4ID0gIkRheSBvZiB5ZWFyIiwKICAgICAgIHkgPSAibG9nKFBNMi41KSIsCiAgICAgICBjYXB0aW9uID0gIk1vZGVsbGVkIGVzdGltYXRlcyByZXN0cmljdGVkIHRvIHdpdGhpbiBjbHVzdGVycyIpICsKICB0aGVtZV9nZ2Rpc3QoKSArCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleTc4IiksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQoKYGBgCgojIyMgTW9kZWwgMjogV2l0aCBjb3ZhcmlhdGVzCgpOb3cgd2UgaW5jbHVkZSBncmlkIGxldmVsIGNvdmFyaWF0ZXMgb2YgZGlzdGFuY2UgdG8gdGhlIHJvYWQsIHBvcHVsYXRpb24gZGVuc2l0eSwgYW5kIGJ1aWxkaW5nIGZvb3RwcmludCBwZXJjZW50LCBhbG9uZyB3aXRodCB0aGUgbWVhbiB0ZW1wIGFuZCBodWlkaXR5IG9uIHRoZSBkYXkgb2YgbWVhc3VyZW1lbnQgKGZyb20gdGhlIHB1cnBsZSBhaXIgbW9uaXRvcnMpCgpBZ2FpbiwgcHJpb3JzIHRvIGJlIGZpeGVkIGxhdGVyCgpgYGB7ciwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTEyfQpwcmlvcnMgPC0gYygKICBwcmlvcihub3JtYWwoMy40LCAxKSwgY2xhc3MgPSAiSW50ZXJjZXB0IiksCiAgcHJpb3Iobm9ybWFsKDAsMSksIGNsYXNzID0gImIiKSwKICBwcmlvcihleHBvbmVudGlhbCgxKSwgY2xhc3MgPSAic2lnbWEiKSwKICBwcmlvcihleHBvbmVudGlhbCgxKSwgY2xhc3MgPSAic2RncCIpLAogIHByaW9yKG5vcm1hbCgwLCAxKSwgY2xhc3MgPSAibHNjYWxlIiwgbGIgPSAwKQopCgoKbTIgPC0gYnJtKAogICAgZm9ybXVsYSA9IGxvZ19wbTJfNSB+IGdwKHgsIHksIGs9MTUpICArCiAgICAgIHMobWVhbl90ZW1wX2MsIGs9NSkgKyAKICAgICAgcyhtZWFuX2N1cnJlbnRfaHVtaWRpdHksIGs9NSkgKwogICAgICBzKHBvcF9kZW5zaXR5X2ttMiwgaz01KSArCiAgICAgIHMoYnVpbGRpbmdfY292ZXJhZ2VfcGN0LCBrPTUpICsKICAgICAgcyhkaXN0X3RvX3JvYWRfbSwgaz01KSArCiAgICAgIHNpbl9kb3kxICsgY29zX2RveTEgKwogICAgICBzaW5fZG95MiArIGNvc19kb3kyICsKICAgICAgc2luX2RveTMgKyBjb3NfZG95MywKICAgIGRhdGEgPSBhcV9tb2RlbF9kYXRhLAogICAgZmFtaWx5ID0gZ2F1c3NpYW4oKSwKICAgIHByaW9yID0gcHJpb3JzLAogICAgY2hhaW5zID0gNCwgY29yZXMgPSA0LAogICAgYmFja2VuZCA9ICJjbWRzdGFuciIKICApCgpzdW1tYXJ5KG0yKQpjb25kaXRpb25hbF9lZmZlY3RzKG0yKQpwcF9jaGVjayhtMikKcGxvdChtMikKCgpgYGAKCmBgYHtyLCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9MTJ9CgojIG0yX2RyYXdzIDwtIGFzX2RyYXdzX2RmKG0yKQojIAojICMgRXh0cmFjdCBwcmlvciBhbmQgcG9zdGVyaW9yIGRyYXdzCiMgcG93ZXJzY2FsZV9zZW5zaXRpdml0eShtMiwgdmFyaWFibGUgPSBjKCJiX0ludGVyY2VwdCIsICJiX3Npbl9kb3kxIiwgImJfY29zX2RveTEiLCAiYl9zaW5fZG95MiIsICJiX2Nvc19kb3kyIiwgImJfc2luX2RveTMiLCAiYl9jb3NfZG95MyIsICJic19zbWVhbl90ZW1wX2NfMSIsICJic19zbWVhbl9jdXJyZW50X2h1bWlkaXR5XzEiLCAiYnNfc3BvcF9kZW5zaXR5X2ttMl8xIiwgImJzX3NidWlsZGluZ19jb3ZlcmFnZV9wY3RfMSIsICJic19zZGlzdF90b19yb2FkX21fMSIsICAgICAgICAgCiMgInNkc19zbWVhbl90ZW1wX2NfMSIsICJzZHNfc21lYW5fY3VycmVudF9odW1pZGl0eV8xIiwgInNkc19zcG9wX2RlbnNpdHlfa20yXzEiLCAic2RzX3NidWlsZGluZ19jb3ZlcmFnZV9wY3RfMSIsIAojICJzZHNfc2Rpc3RfdG9fcm9hZF9tXzEiLCAic2RncF9ncHh5IiwgImxzY2FsZV9ncHh5IiwgInNpZ21hIiwgIkludGVyY2VwdCIsICJzX3NtZWFuX3RlbXBfY18xWzFdIiwgInNfc21lYW5fdGVtcF9jXzFbMl0iLCAic19zbWVhbl90ZW1wX2NfMVszXSIsICAgICAgICAgInNfc21lYW5fY3VycmVudF9odW1pZGl0eV8xWzFdIiwgICJzX3NtZWFuX2N1cnJlbnRfaHVtaWRpdHlfMVsyXSIsICAic19zbWVhbl9jdXJyZW50X2h1bWlkaXR5XzFbM10iLCAgInNfc3BvcF9kZW5zaXR5X2ttMl8xWzFdIiwgICAgICAgCiMgICJzX3Nwb3BfZGVuc2l0eV9rbTJfMVsyXSIsICAgICAgICAic19zcG9wX2RlbnNpdHlfa20yXzFbM10iLCAgICAgICAgInNfc2J1aWxkaW5nX2NvdmVyYWdlX3BjdF8xWzFdIiwgICJzX3NidWlsZGluZ19jb3ZlcmFnZV9wY3RfMVsyXSIsIAojICAic19zYnVpbGRpbmdfY292ZXJhZ2VfcGN0XzFbM10iLCAgInNfc2Rpc3RfdG9fcm9hZF9tXzFbMV0iLCAic19zZGlzdF90b19yb2FkX21fMVsyXSIsICJzX3NkaXN0X3RvX3JvYWRfbV8xWzNdIikpCiMgCiMgcG93ZXJzY2FsZV9wbG90X2RlbnMobTIsIHZhcmlhYmxlID0gYygiYl9JbnRlcmNlcHQiLCAiYl9zaW5fZG95MSIsICJiX2Nvc19kb3kxIiwgImJfc2luX2RveTIiLCAiYl9jb3NfZG95MiIsICJiX3Npbl9kb3kzIiwgImJfY29zX2RveTMiLCAiYnNfc21lYW5fdGVtcF9jXzEiLCAiYnNfc21lYW5fY3VycmVudF9odW1pZGl0eV8xIiwgImJzX3Nwb3BfZGVuc2l0eV9rbTJfMSIsICJic19zYnVpbGRpbmdfY292ZXJhZ2VfcGN0XzEiLCAiYnNfc2Rpc3RfdG9fcm9hZF9tXzEiLCAgICAgICAgIAojICJzZHNfc21lYW5fdGVtcF9jXzEiLCAic2RzX3NtZWFuX2N1cnJlbnRfaHVtaWRpdHlfMSIsICJzZHNfc3BvcF9kZW5zaXR5X2ttMl8xIiwgInNkc19zYnVpbGRpbmdfY292ZXJhZ2VfcGN0XzEiLCAKIyAic2RzX3NkaXN0X3RvX3JvYWRfbV8xIiwgInNkZ3BfZ3B4eSIsICJsc2NhbGVfZ3B4eSIsICJzaWdtYSIsICJJbnRlcmNlcHQiLCAic19zbWVhbl90ZW1wX2NfMVsxXSIsICJzX3NtZWFuX3RlbXBfY18xWzJdIiwgInNfc21lYW5fdGVtcF9jXzFbM10iLCAgICAgICAgICJzX3NtZWFuX2N1cnJlbnRfaHVtaWRpdHlfMVsxXSIsICAic19zbWVhbl9jdXJyZW50X2h1bWlkaXR5XzFbMl0iLCAgInNfc21lYW5fY3VycmVudF9odW1pZGl0eV8xWzNdIiwgICJzX3Nwb3BfZGVuc2l0eV9rbTJfMVsxXSIsICAgICAgIAojICAic19zcG9wX2RlbnNpdHlfa20yXzFbMl0iLCAgICAgICAgInNfc3BvcF9kZW5zaXR5X2ttMl8xWzNdIiwgICAgICAgICJzX3NidWlsZGluZ19jb3ZlcmFnZV9wY3RfMVsxXSIsICAic19zYnVpbGRpbmdfY292ZXJhZ2VfcGN0XzFbMl0iLCAKIyAgInNfc2J1aWxkaW5nX2NvdmVyYWdlX3BjdF8xWzNdIiwgICJzX3NkaXN0X3RvX3JvYWRfbV8xWzFdIiwgInNfc2Rpc3RfdG9fcm9hZF9tXzFbMl0iLCAic19zZGlzdF90b19yb2FkX21fMVszXSIpKQpgYGAKCgpHZXQgcHJlZGljdGlvbnMgZm9yIG1vZGVsIDIKCmBgYHtyfQptMl9zdW0gPC0gZ2V0X3N0X3ByZWRpY3Rpb25zKG1vZGVsID0gbTIsIG5kPW5kX20yLCBtb2RlbF9pZCA9ICJtMiIsIG91dF9kaXIgPSAiZXByZWRfY2h1bmtzX20yIiwgbmRyYXdzID0gMTAwMCwgY2h1bmtfc2l6ZSA9IDEwMDAwLCB3aG9fcG0yNV9saW1pdCA9IDI1KQoKYGBgCgpQbG90IHByZWRpY3Rpb25zIGZvciBtb2RlbCAyCgpgYGB7cn0KbTJfcGxvdHMgPC0gcGxvdF9zdF9wcmVkaWN0aW9ucyhzdW1tYXJ5X2RmID0gbTJfc3VtLCBjbHVzdGVyX3NmID0gc2NhbGVfNzJfY2x1c3RlcnMpCgptMl9wbG90cyAlPiUgbWFwKHBsb3QpCgpgYGAKCgpTYW1wbGUgY29vcmRpbmF0ZXMsIGFuZCBwbG90IG92ZXIgdGltZQoKYGBge3IsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD0xMn0KCiNzYW1wbGUgY29vcmRpbmF0ZSBwb2ludHMKI3dpbGwgc29ydCBvdXQgdGhlIHNtYWxsIG51bWJlciBsYXRlcgpzZXQuc2VlZCgxMjMpCnNhbXBsZWRfcG9pbnRzX20yIDwtIGFxX21vZGVsX2RhdGEgJT4lCiAgc2FtcGxlX24oNTApICU+JQogIHNlbGVjdCh4LCB5LCBtZWFuX3RlbXBfYywgbWVhbl9jdXJyZW50X2h1bWlkaXR5LCBwb3BfZGVuc2l0eV9rbTIsIGJ1aWxkaW5nX2NvdmVyYWdlX3BjdCwgZGlzdF90b19yb2FkX20sIGdyaWRfaWQsIGxvZ19wbTJfNSkKCiNnZW5lcmF0ZSBET1kgMOKAkzM2NSBhbmQgY2FsY3VsYXRlIEZvdXJpZXIgdGVybXMKZG95X2dyaWQgPC0gdGliYmxlKGRveSA9IHNlcSgwLDM2NSwgYnk9MSkpICU+JQogIG11dGF0ZSgKICAgIHNpbl9kb3kxID0gc2luKDIgKiBwaSAqIGRveSAvIDM2NSksCiAgICBjb3NfZG95MSA9IGNvcygyICogcGkgKiBkb3kgLyAzNjUpLAogICAgc2luX2RveTIgPSBzaW4oNCAqIHBpICogZG95IC8gMzY1KSwKICAgIGNvc19kb3kyID0gY29zKDQgKiBwaSAqIGRveSAvIDM2NSksCiAgICBzaW5fZG95MyA9IHNpbig2ICogcGkgKiBkb3kgLyAzNjUpLAogICAgY29zX2RveTMgPSBjb3MoNiAqIHBpICogZG95IC8gMzY1KQogICkKCiNleHBhbmQgZ3JpZCBhY3Jvc3Mgc2FtcGxlZCBsb2NhdGlvbnMKcHJlZGljdGlvbl9kZl9tMiA8LSBzYW1wbGVkX3BvaW50c19tMiAlPiUKICBjcm9zc2luZyhkb3lfZ3JpZCkKCiNhZGQgcHJlZGljdGlvbnMKcHJlZHNfbTIgPC0gYWRkX2VwcmVkX2RyYXdzKG9iamVjdCA9IG0yLCBuZXdkYXRhID0gcHJlZGljdGlvbl9kZl9tMiwgbmRyYXdzID0gMTAwKQoKI29ic2VydmVkIGRhdGEgZm9yIHBsb3R0aW5nCm9ic2VydmVkX2RhdGEgPC0gYXFfbW9kZWxfZGF0YSAlPiUKICBzZWxlY3QobWVhbl9kb3ksIGxvZ19wbTJfNSkgJT4lCiAgbXV0YXRlKG1lYW5fZG95ID0gcm91bmQobWVhbl9kb3kpKQoKI3Bsb3QKcHJlZHNfbTIgJT4lCiAgdW5ncm91cCgpICU+JQogIGdncGxvdChhZXMoeD1kb3kpKSArCiAgc3RhdF9saW5lcmliYm9uKGFlcyh5PS5lcHJlZCksIC53aWR0aD0wLjk1KSArIAogIGdlb21faml0dGVyKGRhdGEgPSBvYnNlcnZlZF9kYXRhLCBhZXMoeCA9IG1lYW5fZG95LCB5ID0gbG9nX3BtMl81KSwKICAgICAgICAgICAgICBjb2xvciA9ICJkYXJrcmVkIiwgYWxwaGEgPSAwLjYsIHdpZHRoID0gMC41LCBoZWlnaHQgPSAwLjAsIHNpemUgPSAxLjIpICsKICBzY2FsZV9maWxsX2JyZXdlcigpICsKICBsYWJzKHRpdGxlID0gIk1vZGVsLWVzdGltYXRlZCBsb2coUE0yLjUpIHdpdGggZW1waXJpY2FsIG1lYXN1cmVtZW50cyIsCiAgICAgICBzdWJ0aXRsZSA9ICJNb2RlbCBwcmVkaWN0aW9ucyB3aXRoIDk1JSBDckkgYW5kIG9ic2VydmVkIGRhdGEgcG9pbnRzIiwKICAgICAgIHggPSAiRGF5IG9mIHllYXIiLAogICAgICAgeSA9ICJsb2coUE0yLjUpIiwKICAgICAgIGNhcHRpb24gPSAiTW9kZWxsZWQgZXN0aW1hdGVzIHJlc3RyaWN0ZWQgdG8gd2l0aGluIGNsdXN0ZXJzIikgKwogIHRoZW1lX2dnZGlzdCgpICsKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5NzgiKSwKICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgoKCmBgYAoKCkNvbXBhcmUgbW9kZWxzLCB1c2luZyBMT08gQ1YKCmBgYHtyfQoKbGlicmFyeShsb28pCgpsb29fbTEgPC0gbG9vKG0xKQpsb29fbTIgPC0gbG9vKG0yKQoKbG9vX2NvbXBhcmUobG9vX20xLCBsb29fbTIpCmBgYAoKCiMjIFBNMTAKCk5vdyB0byBtb2RlbCBQTTEwIGRhdGEKCgoKVE9ETwoKIC0gT1RIRVIgT1VUQ09NRSBNRUFTVVJFUyAoUE0xLCBQTTEwLCBOTykKIC0gQ0FOIFdFIExJTksgVE8gVEhFIFNUQVRJQyBBUSBNT05JVE9SUyAoT1VURE9PUlMpCiAtIFBSSU9SUwogLSBPVEhFUiBDT1ZBUklBVEVTCiAtIENPTVBBUkUgTU9ERUxTIFdJVEggRElGRkVSRU5UIEJBU0lTIEZVTkNUSU9OUyBGT1IgR1AgQU5EIFNQTElORVMKIC0gRVhDRUVEQU5DRVMKIC0gTElOSyBUTyBNVEIgSU5GRUNUSU9OIFBSRVZBTEVOQ0UgREFUQSwgQU5EIE1PREVMCg==